#ifndef _WINDOWS
#include "mermaid.h"
#endif
#include <mml/polygon.h>
#include "Panel.h"
#include "assert.h"

using xxship::Panel;

/** Default constructor. */
Panel::Panel()
    : m_nVertices(0)
    , m_vertices()
    , m_centroid()
    , m_surfaceVector()
    , m_center()
    , m_dArea(0)
    , m_dProjectedArea(0)
    , m_dMinX(DBL_MAX)
    , m_dMaxX(DBL_MAX)
    , m_dMinY(DBL_MAX)
    , m_dMaxY(DBL_MAX)
    , m_dMinZ(DBL_MAX)
    , m_dMaxZ(DBL_MAX)
{}

/** Copy constructor. */
Panel::Panel(Panel const& other)
  : m_nVertices(other.m_nVertices)
  , m_vertices()
  , m_centroid(other.m_centroid)
  , m_surfaceVector(other.m_surfaceVector)
  , m_center(other.m_center)
  , m_dArea(other.m_dArea)
  , m_dProjectedArea(other.m_dProjectedArea)
  , m_dMinX(other.m_dMinX)
  , m_dMaxX(other.m_dMaxX)
  , m_dMinY(other.m_dMinY)
  , m_dMaxY(other.m_dMaxY)
  , m_dMinZ(other.m_dMinZ)
  , m_dMaxZ(other.m_dMaxZ)
{
    // Copy vertex array:
    for (int i = m_nVertices - 1; i >= 0; --i)
    {
        m_vertices[i] = other.m_vertices[i];
    }
}

/** Destructor. */
Panel::~Panel()
{}

/** Copy assignment operator. */
Panel const& Panel::operator=(Panel const& other)
{
    new (this) Panel(other);
    return *this;
}

/** Clear the panel. */
void Panel::clear()
{
    m_nVertices = 0;
    m_dArea = 0.0;
    m_dProjectedArea = 0.0;
    m_dMinX = DBL_MAX;
    m_dMaxX = DBL_MAX;
    m_dMinY = DBL_MAX;
    m_dMaxY = DBL_MAX;
    m_dMinZ = DBL_MAX;
    m_dMaxZ = DBL_MAX;
}

/** Number of vertices. */
int Panel::getNumberOfVertices() const
{
    return m_nVertices;
}

/** Centroid. */
mml::Vector3d const& Panel::getCentroid() const
{
    return m_centroid;
}



/** Normal vector. */
mml::Vector3d const& Panel::getSurfaceVector() const
{
    return m_surfaceVector;
}

/** Area. */
double Panel::getArea() const
{
    return m_dArea;
}

/** Projected Area. */
double Panel::getProjectedArea() const
{
    return m_dProjectedArea;
}

/** Get vertex. */
mml::Vector3d const& Panel::getVertex(int i) const
{
#ifdef _DEBUG
    if (i < 0 || i >= m_nVertices)
    {
        throw mml::IndexOutOfBounds();
    }
#endif

    return m_vertices[i];
}

/** Add vertex. */
void Panel::addVertex(mml::Vector3d const& v)
{
#ifdef _DEBUG
    if (m_nVertices == MAX_VERTICES)
    {
        throw mml::SizeMismatch();
    }
#endif

    m_vertices[m_nVertices] = v;
    ++m_nVertices;
}

/** Compute center, normal and area from current vertices. */
void Panel::compute()
{
    // Copy vertex data into array:
    double vertexData[3 * MAX_VERTICES];
    for (int i = 0; i < m_nVertices; ++i)
    {
        mml::vector_copy(3, &vertexData[3 * i], m_vertices[i].getData());
    }

    mml::analysePolygon(m_nVertices, vertexData,
                        m_surfaceVector.getData(), m_centroid.getData(),
                        m_dArea, m_dProjectedArea);
    m_surfaceVector *= m_dArea;
    computeExtremities();
}

/** Flatten and compute the polygon. */
void Panel::flatten()
{
    // Copy vertex data into array:
    double vertexData[3 * MAX_VERTICES];
    for (int i = 0; i < m_nVertices; ++i)
    {
        mml::vector_copy(3, &vertexData[3 * i], m_vertices[i].getData());
    }

    mml::flattenPolygon(m_nVertices, vertexData,
                        m_surfaceVector.getData(), m_centroid.getData(),
                        m_dArea, m_dProjectedArea);
    m_surfaceVector *= m_dArea;

    // Copy vertex data back into vertices:
    for (int i = 0; i < m_nVertices; ++i)
    {
        mml::vector_copy(3, m_vertices[i].getData(), &vertexData[3 * i]);
    }
    computeExtremities();
}

/** Reduced panel (part above plane). */
Panel Panel::createReducedPanel(mml::Planed const& plane) const
{
    // Allocate vertex array one element bigger:
    double vertexData[2 * 3 * MAX_VERTICES];
    for (int i = 0; i < m_nVertices; ++i)
    {
        mml::vector_copy(3, &vertexData[3 * i], m_vertices[i].getData());
    }

    // Reduce the vertex array:
    int m = mml::reducePolygon(m_nVertices, vertexData,
                               plane.getNormal().getData(),
                               plane.distance());

    if (m == 0)  // Panel entirely below plane, return empty panel:
    {
        return Panel();
    }

    if (m == -1) // Panel entirely above plane, return copy:
    {
        return Panel(*this);
    }

#ifdef DEBUG
    if (m > MAX_VERTICES)
    {
        throw mml::SizeMismatch();
    }
#endif

    // The plane intersects the panel.

    // Create new reduced panel:
    Panel reduced;
    reduced.m_nVertices = m;
    for (int i = 0; i < reduced.m_nVertices; ++i)
    {
        mml::vector_copy(3, reduced.m_vertices[i].getData(), &vertexData[3 * i]);
    }

    // Recompute centroid and normal:
    reduced.compute();

    return reduced;
}

/** Reduce panel (potatocutter in XY-plane). */
Panel Panel::createReducedPanel(const mml::Planed* pPlanes[]) const
{
    // Allocate vertex array one element bigger:
    double vertexData[2 * 3 * MAX_VERTICES];
    for (int i = 0; i < m_nVertices; ++i)
    {
        mml::vector_copy(3, &vertexData[3 * i], m_vertices[i].getData());
    }

    int n = 4; // four planes
    int m = m_nVertices;
    bool chopped = false;
    // loop over all planes
    for (int i = 0; m != 0 && i < n; ++i)
    {
        // Reduce the vertex array:
        int mm = m; // remember vertices
        m = mml::reducePolygon(m, vertexData,
                               pPlanes[i]->getNormal().getData(),
                               pPlanes[i]->distance());
        if (m > 0) // panel cut by plane
        {
            chopped = true;
        }
        if (m == -1) // entirely above plane, no cut
        {
            m = mm;
        }
        // m == 0 -> entirely below plane, discard
    }

#ifdef DEBUG
    if (m > MAX_VERTICES)
    {
        throw mml::SizeMismatch();
    }
#endif

    if (!m)
    {
        return Panel();
    }
    if (!chopped)
    {
        return Panel(*this);
    }

    xxship::Panel reduced;
    reduced.m_nVertices = m;
    for (int i = 0; i < m; ++i)
    {
        mml::vector_copy(3, reduced.m_vertices[i].getData(), &vertexData[3 * i]);
    }

    // Recompute centroid and normal:
    reduced.compute();

    return reduced;
}

/** Rotate panel. */
void Panel::rotate(mml::Matrix3d const& R)
{
    for (int i = 0; i < m_nVertices; ++i)
    {
        m_vertices[i] = R * m_vertices[i];
    }
    m_centroid = R * m_centroid;
    m_surfaceVector = R * m_surfaceVector;
    computeExtremities();
}

/** Translate panel. */
void Panel::translate(mml::Vector3d const& translation)
{
    for (int i = 0; i < m_nVertices; ++i)
    {
        m_vertices[i] += translation;
    }
    m_centroid += translation;
}

/** Minimum x-coordinate. */
double Panel::getMinX() const
{
    return m_dMinX;
}

/** Maximum x-coordinate. */
double Panel::getMaxX() const
{
    return m_dMaxX;
}

/** Minimum y-coordinate. */
double Panel::getMinY() const
{
    return m_dMinY;
}

/** Maximum y-coordinate. */
double Panel::getMaxY() const
{
    return m_dMaxY;
}



/** Minimum z-coordinate. */
double Panel::getMinZ() const
{
    return m_dMinZ;
}

/** Maximum z-coordinate. */
double Panel::getMaxZ() const
{
    return m_dMaxZ;
}

/** Bounding box in XY-plane */
void Panel::getBoundingBox(double* pMinX, double* pMinY, double* pMaxX, double* pMaxY) const
{
    *pMinX = m_dMinX;
    *pMaxX = m_dMaxX;
    *pMinY = m_dMinY;
    *pMaxY = m_dMaxY;
}

/** Average vertex position. */
mml::Vector3d Panel::getCenter() const
{
    return m_center;
}

/** Find a vertex. */
int Panel::find(mml::Vector3d const& v)
{
    for (int i = 0; i < m_nVertices; ++i)
    {
        if (v == m_vertices[i])
        {
            return i;  // found
        }
    }
    return -1;  // not found
}

void Panel::computeExtremities()
{
    m_center.reset();
    m_dMinX =  DBL_MAX;
    m_dMinY =  DBL_MAX;
    m_dMinZ =  DBL_MAX;
    m_dMaxX = -DBL_MAX;
    m_dMaxY = -DBL_MAX;
    m_dMaxZ = -DBL_MAX;
    for (int i = 0; i < m_nVertices; ++i)
    {
        if (m_vertices[i](0) > m_dMaxX)
        {
            m_dMaxX = m_vertices[i](0);
        }
        if (m_vertices[i](0) < m_dMinX)
        {
            m_dMinX = m_vertices[i](0);
        }
        if (m_vertices[i](1) > m_dMaxY)
        {
            m_dMaxY = m_vertices[i](1);
        }
        if (m_vertices[i](1) < m_dMinY)
        {
            m_dMinY = m_vertices[i](1);
        }
        if (m_vertices[i](2) > m_dMaxZ)
        {
            m_dMaxZ = m_vertices[i](2);
        }
        if (m_vertices[i](2) < m_dMinZ)
        {
            m_dMinZ = m_vertices[i](2);
        }
        m_center += m_vertices[i];
    }
    m_center /= static_cast<double>(m_nVertices);
}
